<?php


namespace XinSheng;

use Fize\Datetime\Date;
use Fize\Math\Financial;

/**
 * 内部年化IRR定价模型
 */
class ModelByIrr
{

    /**
     * @var float 转让价款
     */
    protected $leaseAmount;

    /**
     * @var float 首付款
     */
    protected $downPayment;

    /**
     * @var float 预付款
     */
    protected $deposit;

    /**
     * @var float 留购价
     */
    protected $residualValue;

    /**
     * @var float 手续费
     */
    protected $serviceAmount;

    /**
     * @var float 内部年化IRR
     */
    protected $interestRate;

    /**
     * @var int 期次
     */
    protected $issues;

    /**
     * @var array 其他费用
     */
    protected $otherfees;

    /**
     * @var float|null 首次利息本金比
     */
    protected $interestRateFirst;

    /**
     * @var int 每期月数
     */
    protected $issueMonths;

    /**
     * @var int 支付方式：0-期末支付；1-期初支付
     */
    protected $paymentMode;

    /**
     * @var float 每期利率
     */
    protected $issueRate;

    /**
     * @var int 保留小数位
     */
    protected $scale;

    /**
     * @var float 每期租金
     */
    protected $rent;

    /**
     * @var float 外部年化IRR
     */
    protected $outsideRate;

    /**
     * 初始化
     * @param float      $leaseAmount       转让价款
     * @param float      $downPayment       首付款
     * @param float      $deposit           预付款
     * @param float      $residualValue     留购价
     * @param float      $serviceAmount     手续费
     * @param float      $interestRate      内部年化IRR
     * @param int        $issues            期次
     * @param array      $otherfees         其他费用
     * @param float|null $interestRateFirst 首次利息本金比，null表示不指定
     * @param int        $paymentMode       支付方式：0-期末支付；1-期初支付
     * @param int        $issueMonths       每期月数
     * @param int        $scale             保留小数位
     */
    public function __construct(
        $leaseAmount, $downPayment, $deposit, $residualValue, $serviceAmount,
        $interestRate, $issues, $otherfees = [], $interestRateFirst = null, $paymentMode = 0,
        $issueMonths = 1, $scale = 2
    )
    {
        $this->leaseAmount = $leaseAmount;
        $this->downPayment = $downPayment;
        $this->deposit = $deposit;
        $this->residualValue = $residualValue;
        $this->serviceAmount = $serviceAmount;
        $this->interestRate = $interestRate;
        $this->issues = $issues;
        $this->otherfees = $otherfees;
        $this->interestRateFirst = $interestRateFirst;
        $this->paymentMode = $paymentMode;
        $this->issueMonths = $issueMonths;

        $this->issueRate = $this->interestRate * $this->issueMonths / 12;
        $this->scale = $scale;
    }

    /**
     * 获取每期租金
     * @return float
     */
    public function getRent()
    {
        if (is_null($this->rent)) {
            $rentByIrr = new RentByIrr(
                $this->leaseAmount, $this->downPayment, $this->deposit, $this->residualValue, $this->serviceAmount,
                $this->interestRate, $this->issues, $this->otherfees, $this->paymentMode, $this->issueMonths,
                $this->scale
            );
            $this->rent = $rentByIrr->getRent();
        }
        return $this->rent;
    }

    /**
     * 获取还租计划
     * @param string|null $startDate 起租日，指定起租日则还款计划带有还款日期
     * @return array
     */
    public function getPlan($startDate = null)
    {
        $rent = $this->getRent();
        $plan = [];
        for ($i = 0; $i <= $this->issues; $i++) {
            $plan[$i]['issue'] = $i;
            if (!empty($startDate)) {
                $plan[$i]['payment_date'] = $i == 0 ? $startDate : Date::getAfter($startDate, $i * $this->issueMonths);
            }
            if ($i == 0) {
                $plan[0]['rent'] = $this->paymentMode == 1 ? $rent : 0;
                $plan[0]['capital'] = $plan[0]['rent'];
                $plan[0]['interest'] = 0;
                $plan[0]['balance'] = round($this->leaseAmount - $this->downPayment - $plan[0]['capital'], $this->scale);  // 剩余本金 = 概算本金 - 本金
                $plan[0]['cash_flow'] = -round($this->leaseAmount - $this->downPayment - $this->deposit - $plan[0]['rent'] - $this->serviceAmount, $this->scale);  // 现金流 = -(转让价款 - 首付款 - 预付款 - 租前租金 - 手续费
                $plan[0]['nominal_cash_flow'] = -$plan[0]['balance'];  // 名义现金流 = -剩余本金
                $plan[0]['residual_value'] = 0;  // 留购价另算
                $plan[0]['deposit_deduction'] = 0;  // 预付款抵扣另算
                $plan[0]['otherfee'] = 0;  // 其他费用另算
                $plan[0]['payable_amount'] = $plan[0]['cash_flow'];  // 应付金额 = 转让价款 - 首付租金 - 预付款 - 租前租金 - 手续费
            } else {
                $plan[$i]['rent'] = $this->paymentMode == 1 && $i == $this->issues ? 0 : $rent;
                // 以下3项先置0，另算
                $plan[$i]['capital'] = 0;
                $plan[$i]['interest'] = 0;
                $plan[$i]['balance'] = 0;
                // 以下2项此时仅算租金
                $plan[$i]['cash_flow'] = ($this->paymentMode == 1 && $i == $this->issues) ? 0 : $rent;  // 现金流 = 租金 - 预付款抵扣 + 留购
                $plan[$i]['nominal_cash_flow'] = ($this->paymentMode == 1 && $i == $this->issues) ? 0 : $plan[$i]['rent'];  // 名义现金流 = 租金 + 留购
                $plan[$i]['residual_value'] = 0;  // 留购价另算
                $plan[$i]['deposit_deduction'] = 0;  // 预付款抵扣另算
                $plan[$i]['otherfee'] = 0;  // 其他费用另算
                $plan[$i]['payable_amount'] = 0;  // 应付金额另算
            }
        }
        // 预付款抵扣
        $start = $this->paymentMode == 1 ? $this->issues - 1 : $this->issues;
        $deposit = $this->deposit;
        for ($i = $start; $i > 0; $i--) {
            if ($deposit <= 0) break;
            if ($plan[$i]['cash_flow'] <= $deposit) {
                $plan[$i]['deposit_deduction'] = $plan[$i]['cash_flow'];
                $plan[$i]['cash_flow'] = 0;
            } else {
                $plan[$i]['deposit_deduction'] = $deposit;
                $plan[$i]['cash_flow'] = round($plan[$i]['cash_flow'] - $deposit, $this->scale);
            }
            $deposit = round($deposit - $plan[$i]['deposit_deduction'], $this->scale);
        }
        // 留购价
        $plan[$this->issues]['residual_value'] = round($plan[$this->issues]['residual_value'] + $this->residualValue, $this->scale);
        $plan[$this->issues]['cash_flow'] = round($plan[$this->issues]['cash_flow'] + $this->residualValue, $this->scale);
        $plan[$this->issues]['nominal_cash_flow'] = round($plan[$this->issues]['nominal_cash_flow'] + $this->residualValue, $this->scale);
        // 首期固定利息，本金、利息、剩余本金
        if (!is_null($this->interestRateFirst)) {
            $interest = round(round($this->leaseAmount - $this->downPayment, $this->scale) * $this->interestRateFirst, $this->scale);  // 概算本金 * 首期利率
            $capital = round($plan[1]['rent'] - $interest, $this->scale);
            $balance = round($plan[0]['balance'] - $capital, $this->scale);
            $plan[1]['capital'] = $capital;
            $plan[1]['interest'] = $interest;
            $plan[1]['balance'] = $balance;
        }
        $outsideIrr = is_null($this->interestRateFirst) ? $this->getOutsideIrr($plan) : $this->getOutsideIrr2($plan);  // 外部IRR
        $end = $this->paymentMode == 1 ? $this->issues - 1 : $this->issues;
        for ($i = 1; $i <= $end; $i++) {
            if (!is_null($this->interestRateFirst) && $i == 1) {  // 首期固定利息
                continue;
            }
            if ($i == $end) {
                $capital = round($plan[$i - 1]['balance'] - $this->residualValue, $this->scale);
                $interest = round($plan[$i]['rent'] - $capital, $this->scale);
            } else {
                $interest = round($plan[$i - 1]['balance'] * $outsideIrr, $this->scale);
                $capital = round($plan[$i]['rent'] - $interest, $this->scale);
            }
            $balance = round($plan[$i - 1]['balance'] - $capital, $this->scale);
            $plan[$i]['capital'] = $capital;
            $plan[$i]['interest'] = $interest;
            $plan[$i]['balance'] = $balance;
        }
        // 保存外部年利率
        $this->outsideRate = $outsideIrr * 12 / $this->issueMonths;
        // 其他费用
        if (!empty($this->otherfees)) {
            foreach ($this->otherfees as $fee) {
                $plan[$fee['issue']]['cash_flow'] = round($plan[$fee['issue']]['cash_flow'] + $fee['amount'], $this->scale);
                $plan[$fee['issue']]['otherfee'] = round($plan[$fee['issue']]['otherfee'] + $fee['amount'], $this->scale);
            }
        }
        for ($i = 1; $i <= $this->issues; $i++) {
            // 应付金额 = 租金 - 预付款抵扣 + 留购价 + 其他费用
            $plan[$i]['payable_amount'] = round($plan[$i]['rent'] - $plan[$i]['deposit_deduction'] + $plan[$i]['residual_value'] + $plan[$i]['otherfee'], $this->scale);
        }
        // 修正第0期应付金额
        $plan[0]['payable_amount'] = -$plan[0]['cash_flow'];
        return $plan;
    }

    /**
     * 获取外部年化利率
     * @return float
     */
    public function getOutsideRate()
    {
        if (is_null($this->outsideRate)) {
            $this->getPlan();
        }
        return $this->outsideRate;
    }

    /**
     * 获取外部租赁期利率
     * @param array $plan 还租计划表
     * @return float
     */
    private function getOutsideIrr($plan)
    {
        $flow = [];
        for ($i = 0; $i <= $this->issues; $i++) {
            $flow[$i] = $plan[$i]['nominal_cash_flow'];
        }
        return Financial::irr($flow, $this->interestRate * $this->issueMonths / 12);
    }

    /**
     * 获取外部转换部分的承租人IRR
     * @param array $plan 还租计划表
     * @return float
     */
    private function getOutsideIrr2($plan)
    {
        $flow = [];  // 获取转换后的名义现金流
        $flow[0] = -$plan[1]['balance'];
        $end = $this->paymentMode == 1 ? $this->issues - 1 : $this->issues;
        for ($i = 1; $i <= $end; $i++) {
            $flow[$i] = $plan[$i]['rent'];
            if ($i == $end) {
                $flow[$i] = round($flow[$i] + $this->residualValue, $this->scale);
            }
        }
        return Financial::irr($flow, $this->interestRate * $this->issueMonths / 12);
    }
}